package tetris.agent;

import java.util.Arrays;

import tetris.simulator.State;
import tetris.features.*;


public class ApproxFunction {
	
	public double wtdLinearFeatures(State s, int action, double[] weight) 
	{	
		State newState = new State(s);
		//newState.makeMove(action);

		//double[] allFeatures = getFeatures(newState);
		double[] allFeatures = getFeatures_ftutils(newState, action);

		// wt.features (dot product)
		double Q = dotProduct(allFeatures, weight);

		return Q;
	}


	public double[] delLogPolicy(State s,int[][] legalMoves, int action, double[] weight){
				
		State newState = new State(s);
		newState.makeMove(action);
		
		//double[] fsa = getFeatures(newState);
		double[] fsa = getFeatures_ftutils(newState, action);
		
		double[] delLog_term2 = new double[fsa.length];
		for (int i = 0; i< legalMoves.length; i++){
			State sClone = new State(s);
			double policysa_new = softMaxPDF_forAction(sClone, i, legalMoves, weight);
			
			sClone.makeMove(i);
			//double[] fsa_new = getFeatures(sClone);
			double[] fsa_new = getFeatures_ftutils(sClone, action);
			
			delLog_term2 = sumArrays(delLog_term2, scalarProduct(fsa_new,policysa_new, true),true);
		}
		
		double[] delLog = sumArrays(fsa, delLog_term2, false);
		return delLog;
	}

	public double softMaxPDF_forAction(State s, int action, int[][]legalMoves, double[] weight ){

		// Scale factor: scale by max value to prevent blow up to infinity
		double maxWF = 0.0;
		for (int i = 0; i< legalMoves.length; i++){
			if (Math.abs(wtdLinearFeatures(s, i, weight))>Math.abs(maxWF)){
				maxWF = wtdLinearFeatures(s, i, weight);
			}			
		}

		maxWF = maxWF + 1;
		
//		if (maxWF==0){
//			maxWF = 1;
//		}
		
		// Normalization
		double Z = 0;
		for (int i = 0; i< legalMoves.length; i++){
			double normWF = (double)wtdLinearFeatures(s, i, weight)/maxWF;
			Z += Math.exp(normWF);
			//System.out.println("MaxWF in PI is "+ maxWF+ " exp input in Z PI= "+ (wtdLinearFeatures(s, i, weight)-maxWF));
		}
		//System.out.println("Z PI= "+Z);
		
		// calculate p(a/x)
		double p_ax=0.0;
		double policyFts = (double)wtdLinearFeatures(s, action, weight)/maxWF;
		p_ax = (double)Math.exp(policyFts)/Z;

		return p_ax;
	}
	
	public int softMaxPolicy(State s, int[][] legalMoves, double[] weight ){
		
		// Scale factor: scale by max value to prevent blow up to infinity
		double maxWF = 0.0;
		for (int i = 0; i< legalMoves.length; i++){
			if (Math.abs(wtdLinearFeatures(s, i, weight))>Math.abs(maxWF)){
				maxWF = wtdLinearFeatures(s, i, weight);
			}			
		}
		maxWF = maxWF + 1;
		
//		if (maxWF==0){
//			maxWF = 1;
//		}
		
		// Normalization
		double Z = 0;
		for (int i = 0; i< legalMoves.length; i++){
			double normWF = (double)wtdLinearFeatures(s, i, weight)/maxWF;
			Z += Math.exp(normWF);
		}
		
		//System.out.println("Z= "+Z);
		
		// calculate p(a/x)
		double[] policy = new double[legalMoves.length];
		for (int i = 0; i< legalMoves.length; i++){
			// Wtd Linear Fts for Policy
			double policyFts = (double)wtdLinearFeatures(s, i, weight)/maxWF;
			policy[i] = (double)Math.exp(policyFts)/Z;
		}
		
		// CDF
		double[] cdf = new double[policy.length];
		for(int i=0; i<policy.length;i++){
			if (i==0){
				cdf[i] = policy[i];
			}
			else{
				cdf[i] = cdf[i-1]+policy[i];
			}
		}
		
	   double act = Math.random();
	   
	   int returnAction = 0;
	   boolean found = false;
	   for (int i=0; i<cdf.length;i++){
		   if (!found){
			   if (act<=cdf[i]){
				   returnAction = i;
				   found = true;
			   }
		   }
	   }
	   return returnAction;
	}



	public double[] getFeatures(State s){

		FeatureExtraction features = new FeatureExtraction();

		//get features
		double[] height = features.top(s);
		//double[] heightDiff = features.heightDifference(s);
		//double heightMax = features.maxtop(s);
		//double[] holes = features.holes(s);

		// concatenate all features into a vector/array
		int wtSize = FeatureExtraction.featureSize(0);
		//double[] allFeatures = new double[height.length+heightDiff.length+holes.length+1];
		double[] allFeatures = new double[wtSize];
		System.arraycopy(height, 0, allFeatures, 0, height.length);
		//System.arraycopy(heightDiff, 0, allFeatures, height.length, heightDiff.length);
		//System.arraycopy(holes, 0, allFeatures, height.length, holes.length);
		//allFeatures[allFeatures.length-1]=heightMax;

		return allFeatures;
	}
	
	public double[] getFeatures_ftutils(State s, int action){

		featureutils fts = new featureutils();
		double[] allFeatures = fts.features(s, action);

		return allFeatures;
	}

    // Utility Functions
	public double dotProduct(double[] fts, double[] wt){
		double dtPrd = 0.0;
		for (int i = 0; i < wt.length; i++) {
			dtPrd += fts[i] * wt[i];
		}
		return dtPrd;
	}
	
	public static double[] scalarProduct(double[] Matrix, double K,boolean operator){
		double KPrd[] =  new double[Matrix.length];
		
		// operator = true does scalar product else scalar division
		if (operator==true){
			for (int i = 0; i < Matrix.length; i++) {
				KPrd[i] = K * Matrix[i];
			}
		}
		else{
			for (int i = 0; i < Matrix.length; i++) {
				KPrd[i] = Matrix[i]/K;
			}
		}
		return KPrd;
	}
	
	public static double[] sumArrays(double[] Matrix1, double[] Matrix2, boolean operator){
		double[] sumArr = new double[Matrix1.length];
		
		// operator = true does addition else subtraction
		if (operator==true){
			for (int i = 0; i < Matrix1.length; i++) {
				sumArr[i]=  Matrix1[i] + Matrix2[i];
			}
		}
		else{
			for (int i = 0; i < Matrix1.length; i++) {
				sumArr[i]=  Matrix1[i] - Matrix2[i];
			}
		}
		return sumArr;
	}

}
